local skynet = require "skynet"
local socketdriver = require "skynet.socketdriver"
local netpack = require "skynet.netpack"

local s = require "service"
local conn_factory = require "connection"
local runconfig = require "runconfig"
local table_utils = require "table_utils"
local buffer_handler = require "buffer_handler"
local proto_helper = require "protobuf_helper"
local game_cmd = require "cmd_def"

require "gateway_resp"


local queue
s.conns = {}  -- fd -> conn
s.uid2conn = {}  -- uid -> conn


local function print_debug()
    skynet.error("current conns:" .. table_utils.size(s.conns) .. " info:" .. table_utils.table2string(s.conns))
    
end

local function send_msg_to_client(fd, cmd, msg)    
    s.resp.send_by_fd(_, fd, cmd, msg)
end

s.disconnect = function(fd)
    local c = s.conns[fd]
    if c then
        s.conns[fd] = nil
        local uid = c:get_uid()
        if uid then
            s.uid2conn[uid] = nil
            skynet.call("agent_mgr", "lua", "kick", uid, "disconnect")
        else
            pcall(skynet.call, c:get_agent(), "lua", "kick")
            pcall(skynet.send, c:get_agent(), "lua", "exit")
        end
    end
end

local function process_connect(fd, addr)
    skynet.error("new conn fd:" .. fd .. " addr:" .. addr)
    local conn = conn_factory.new()
    conn:set_fd(fd)
    s.conns[fd] = conn

    socketdriver.start(fd)

    print_debug()
end

local function process_close(fd)
    skynet.error("close fd:" .. fd)
    s.disconnect(fd)
    socketdriver.close(fd)

    print_debug()
end

local function process_error(fd, error)
    skynet.error("error fd:" .. fd .. " error:" .. error)
    s.disconnect(fd)
    socketdriver.close(fd)

    print_debug()
end

local function process_warning(fd, size)
    skynet.error("warning fd:" .. fd .. " size:" .. size)
end

local function process_msg(fd, userdata, sz)
    local c = s.conns[fd]
    if not c then
        skynet.error("process_msg, fd error, not conns, fd:" .. fd)
        return
    end

    -- 原始数据，除去前2字节【cmd+data】
    local cmd_origin_data = netpack.tostring(userdata, sz)
    -- 读取cmd, 解析data
    local cmd, msg = buffer_handler.parse_client_data(cmd_origin_data)
    skynet.error("cmd:" .. cmd .. " msg:" .. table_utils.table2string(msg))
    -- 判断是否登录
    local uid = c.uid
    if not uid then
        -- 判断cmd是否是登录
        if cmd == game_cmd.CLIENT_CMD.LOGIN then
            local node = skynet.getenv("node")
            local node_cfg = runconfig[node]
            local login_id = math.random(1, #node_cfg.login)
            local login_node = "login" .. login_id
            skynet.send(login_node, "lua", "client", fd, cmd, msg)
        else
            -- 通知client需要登录
            send_msg_to_client(fd, game_cmd.SERVER_CMD.COMMON, {
                error_code = game_cmd.ERROR_CODE.PERMISSION_DENIED,
                cmd = cmd,
                msg = "please login first"
            })
        end
    else
        if cmd == game_cmd.CLIENT_CMD.LOGIN then
            -- 同一个连接已经登录过
            send_msg_to_client(fd, game_cmd.SERVER_CMD.COMMON, {
                error_code = game_cmd.ERROR_CODE.PERMISSION_DENIED,
                cmd = cmd,
                msg = "already login"
            })
        else
            local agent = c:get_agent()
            -- 转发给agent执行cmd
            skynet.call(agent, "lua", "client", cmd, msg)
        end
    end
end

local function process_more()
    local fd, msg, sz = netpack.pop(queue)
    if fd then
        -- may dispatch even the handler.message blocked
        -- If the handler.message never block, the queue should be empty, so only fork once and then exit.
        skynet.fork(process_more)
        process_msg(fd, msg, sz)

        for fd, msg, sz in netpack.pop, queue do
            process_msg(fd, msg, sz)
        end
    end
end

local function socket_unpack(msg, sz)
    return netpack.filter(queue, msg, sz)
end

local function socket_dispatch(_, _, q, type, ...)
    skynet.error("socket_dispatch type:" .. (type or "nil"))
    queue = q
    if type == "open" then
        process_connect(...)
    elseif type == "data" then
        process_msg(...)
    elseif type == "more" then
        process_more(...)
    elseif type == "close" then
        process_close(...)
    elseif type == "error" then
        process_error(...)
    elseif type == "warning" then
        process_warning(...)
    end
end

s.init = function()
    skynet.register_protocol({
        name = "socket",
        id = skynet.PTYPE_SOCKET,
        unpack = socket_unpack,
        dispatch = socket_dispatch
    })

    proto_helper.regist_all_proto_files()

    local node = skynet.getenv("node")
    local nodecfg = runconfig[node]
    local port = nodecfg.gateway[s.id].port

    local listenfd = socketdriver.listen("0.0.0.0", port)
    socketdriver.start(listenfd)
    skynet.error("start gateway port:", port)
end

s.start(...)